/**
 * WorkSpace class v0.1.0
 *
 * @author  coder-xiaomo
 * @date    2022-05-15
 *
 * Released under the MIT license
 */
class WorkSpace {
    static settings = null;
    static primaryCanvas = null;

    constructor(settings) {
        this.settings = settings
        settings.workSpace = this

        // 清除原有内容
        settings.container
            .style("width", settings.width)
            .style("height", settings.height)
            // .attr("width", settings.width)
            // .attr("height", settings.height)
            .html("")

        // 创建工作区SVG
        this.primaryCanvas = settings.container.append("svg")
            // 添加id
            .attr("id", "primaryCanvas")
            // 设置 SVG 宽高
            .attr("width", "100%")
            .attr("height", "100%")
            // 背景色
            .style("background-color", settings.colorMap["background"])
    }
}

class ViBase {
    static workSpace = null;
    constructor(workSpace) {
        this.workSpace = workSpace
    }
}
class ArrayVi extends ViBase {
    // [47, 11, 50, 13, 16, 49, 8, 9, 38, 27, 20]
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
    static listData

    // 网页加载完毕初始化事件
    initialize({ elementId }) {
        let controlDiv = document.getElementById("control-div")
        controlDiv.style.textAlign = "center"

        var sortClassList = getSortClassList();
        console.log(sortClassList);
        var DOMFragment = document.createDocumentFragment()

        var selector = document.createElement("select")
        for (let i = 0; i < sortClassList.length; i++) {
            const sortClass = new sortClassList[i](animation)
            const sortClassInfo = sortClass.info()

            // 跳过未完成的算法
            if (!sortClassInfo['available'])
                continue

            let option = document.createElement("option")
            option.value = sortClassInfo['name']
            option.innerText = sortClassInfo['name']
            selector.appendChild(option)
        }
        DOMFragment.appendChild(selector)

        let ctrlBtn = document.createElement("button")
        ctrlBtn.innerHTML = "开始排序"
        let that = this
        ctrlBtn.onclick = function () {
            // 点击排序算法按钮
            // if (!that.updateListDataArray(elementId, { doNotAlert: false }))
            //     return
            if (!that.listData || that.listData.length == 0) {
                alert("数组为空")
                return
            }

            // 隐藏一些东西，显示一些东西
            controlDiv.style.display = 'none'
            d3.select("#console-div")
                .style("display", "")
            d3.select("#console-current-algorithm")
                .style("text-align", "center")
                .html(selector.value/*sortClassInfo['name']*/)

            // 找到对应的算法，然后开始排序
            for (let i = 0; i < sortClassList.length; i++) {
                const sortClass = new sortClassList[i](animation)
                const sortClassInfo = sortClass.info()

                if (sortClassInfo['name'] === selector.value) {
                    sortClass.doSortWithAnimation(elementId)
                    break
                }
            }
        }
        DOMFragment.appendChild(ctrlBtn)

        // 页面最后更新时间
        var lastModifiedTime = (new Date(document.lastModified).getTime() / 1000).toFixed(0)
        let lastModifiedDiv = document.createElement("div")
        lastModifiedDiv.style.fontSize = "x-small"
        lastModifiedDiv.style.opacity = ".5"
        lastModifiedDiv.innerHTML = "页面版本戳: " + lastModifiedTime
        DOMFragment.appendChild(lastModifiedDiv)

        controlDiv.appendChild(DOMFragment)

        // 生成一个随机数组
        this.randomListDataArray(elementId)

        // 显示 siderbar
        d3.select("#sidebar").style("display", "")
    }

    // 将数组显示到输入框中
    updateListDataInput() {
        document.getElementById("array-input").value = this.listData.join("，")
    }

    // Array 内容改变时
    updateListDataArray(elementId, { doNotAlert = false }) {
        var val = document.getElementById("array-input").value
        try {
            var preList = val.replaceAll("，", ",").split(",")
            this.listData = []
            preList.forEach(element => {
                if (element.trim() === "" || isNaN(element))
                    return
                this.listData.push(Number(element))
            });
            arrayVi.initArray(elementId, this.listData)
        } catch (err) {
            console.log(err)
            if (!doNotAlert)
                alert("输入不正确，请检查！")
            return false
        }
        return true
    }

    // 随机 Array
    randomListDataArray(elementId) {
        function getRandom(length) {
            return Math.floor(Math.random() * length);    // 可均衡获取 0 到 length-1 的随机整数。
        }
        // 获取一个 6 - 12 以内的随机数
        var len = 6 + getRandom(13 - 6)

        this.listData = []
        for (let i = 0; i < len; i++) {
            this.listData.push(getRandom(51))
        }
        this.updateListDataInput()
        this.updateListDataArray(elementId, { doNotAlert: false })
    }

    // 绘制数组
    initArray(elementId, listData) {
        console.log("initArray")
        let fragment = shape.getLinkedListFragment(elementId, listData, {
            x: 100,
            y: 100,
            width: "100px",
            height: "100px",
        })

        // console.log(fragment)
        workSpace.primaryCanvas.html("")

        // 添加水印 居中
        var watermarkWidth = settings.outerSize.height * 0.65
        shape.addWatermark(elementId, {
            imageSrc: "./assets/image/logo-small.svg",
        })
            .attr('id', 'watermark-c-c')
            .attr('x', settings.outerSize.width / 2)
            .attr('y', settings.outerSize.height / 2)
            .style('width', watermarkWidth + 'px')
            .style('height', watermarkWidth + 'px')
            .style('transform', `translate(-${watermarkWidth / 2}px, -${watermarkWidth / 2}px)`)
            .style('opacity', '0.015')
            .style('transition', '0.2s')

        // 添加水印 右下角
        var watermarkWidth = 60
        shape.addWatermark(elementId, {
            imageSrc: "./assets/image/logo-small.svg",
        })
            .attr('id', 'watermark-r-b')
            .attr('x', settings.outerSize.width)
            .attr('y', settings.outerSize.height)
            .style('width', watermarkWidth + 'px')
            .style('height', watermarkWidth + 'px')
            .style('transform', 'translate(-80px, -80px)')

        workSpace.primaryCanvas.node().appendChild(fragment)
        document.getElementById(elementId).customAttr = fragment.customAttr
    }
}

/**
 * Shape class v0.1.0
 *
 * @author  coder-xiaomo
 * @date    2022-05-15
 *
 * Released under the MIT license
 */
class Shape {
    static workSpace = null;
    constructor(workSpace) {
        this.workSpace = workSpace
    }

    addShape(shape, id) {
        var settings = this.workSpace.settings
        return workSpace.primaryCanvas.append(shape)
            .style("transform", `translate(${settings.margin.left}px, ${settings.margin.top}px)`)
            .attr("id", id)
    }
    addShape_NoTransform(shape, id) {
        return workSpace.primaryCanvas.append(shape)
            .attr("id", id)
    }

    // 添加矩形
    rectangle(id, { x, y, width, height, fillColor = "white", strokeColor = "black" }) {
        return this.addShape("rect", id)
            .attr("x", x)
            .attr("y", y)
            .attr("width", width)
            .attr("height", height)
            .style("fill", fillColor)
            .style("stroke", strokeColor)
    }

    // 添加圆形
    circle(id, { x, y, radius, fillColor = "white", strokeColor = "black" }) {
        return this.addShape("circle", id)
            .attr("cx", x)
            .attr("cy", y)
            .attr("r", radius)
            .style("fill", fillColor)
            .style("stroke", strokeColor)
    }

    // 添加文本
    text(id, { x, y, text, fillColor }) {
        return this.addShape("text", id)
            .attr("x", x)
            .attr("y", y)
            .text(text)
            .style("fill", fillColor)
    }

    // 添加线
    line(id, { x1, y1, x2, y2, strokeColor }) {
        return this.addShape("line", id)
            .attr("x1", x1)
            .attr("y1", y1)
            .attr("x2", x2)
            .attr("y2", y2)
            .style("stroke", strokeColor)
    }

    // 添加路径
    path(id, d, { fillColor = "white", strokeColor = "black" }) {
        return this.addShape("path", id)
            .attr("d", d)
            .style("fill", fillColor)
            .style("stroke", strokeColor)
    }

    // 添加坐标轴
    axis(id, { transform, axis }) {
        return this.addShape_NoTransform("g", id)
            .attr("transform", transform)
            .call(axis)
    }

    // 添加一个链表节点
    addNode(id, x, y, width, height, text) {
        var primaryCanvas = workSpace.primaryCanvas
        primaryCanvas.append("rect", id)
            .attr("x", x)
            .attr("y", y)
            .attr("width", width)
            .attr("height", height)
            .style("fill", workSpace.settings.colorMap["fill"])
            .style("stroke", workSpace.settings.colorMap["stroke"])
        primaryCanvas.append("text", id + "_text")
            .attr("x", x)
            .attr("y", y)
            // .attr("x", x + width / 2)
            // .attr("y", y + height / 2)
            .text(text)
            .style("fill", workSpace.settings.colorMap["text"])
    }


    // 添加一个图片水印
    addWatermark(id, { imageSrc }) {
        var primaryCanvas = workSpace.primaryCanvas
        return primaryCanvas.append("image", id)
            .attr("xlink:href", imageSrc)
    }

    // 绘制一个链表
    getLinkedListFragment(id, nodes) {
        var settings = this.workSpace.settings

        let displayMaxWidth = 1 * settings.innerSize.width
        let displayMaxHeight = 1 * settings.innerSize.height
        let areaWidth = 1 * nodes.length // 按照1个Unit来计算
        let areaHeight = 1 // 按照1个Unit来计算

        let oneUnit = 100
        // 可以假设高度相等比较宽度，这样好理解
        if (displayMaxWidth / displayMaxHeight > areaWidth / areaHeight) {
            // 展示区域左右有多余空间
            oneUnit = displayMaxHeight / areaHeight
        } else {
            // 展示区域上下有空间（或刚刚好）
            oneUnit = displayMaxWidth / areaWidth
        }

        // 定义最大值
        if (oneUnit > 120) oneUnit = 120

        let fragment = document.createDocumentFragment()
        fragment.customAttr = {
            id: id,
            nodes: nodes,
            type: "linkedList",
            oneUnit: oneUnit,
            gsapTimeline: gsap.timeline({
                onStart: function () {
                    consoleClear()
                },
                onComplete: function () {
                    consoleLog(`排序完成`)
                    console.log("all done")
                    // this.seek(0)
                }
            })
        }
        // console.log(fragment.customAttr)

        // <g></g> 元素不能设置 width 和 height

        let g = d3.select(fragment)
            .append("svg:g")
            .attr("id", id)
            .attr("fill", "white")
            .attr("transform", `translate(${settings.margin.left}, ${settings.margin.top})`)

        for (let i = 0; i < nodes.length; i++) {
            let node = nodes[i]
            let _g = g.append("svg:g")

            _g.append("svg:rect")
                .attr("x", i * oneUnit)
                .attr("y", 0)
                .attr("width", oneUnit)
                .attr("height", oneUnit)
                .attr("fill", settings.colorMap["fill"])
                .attr("stroke", settings.colorMap["stroke"])

            _g.append("svg:text")
                .text(node)
                .attr("x", i * oneUnit + oneUnit / 2)
                .attr("y", oneUnit / 2)
                .attr("width", oneUnit)
                .attr("height", oneUnit)
                .attr("fill", settings.colorMap["text"])

            // 调试用
            if (settings.debugMode)
                _g.append("svg:text")
                    .text(i)
                    .attr("x", i * oneUnit)
                    .attr("y", oneUnit)
                    .attr("width", oneUnit)
                    .attr("height", oneUnit)
                    .attr("fill", "black")

            // console.log(text.node().getBBox())
            // console.log(text.node().getBoundingClientRect())
        }

        // g.append("svg:rect")
        //     .attr("width", oneUnit * nodes.length)
        //     .attr("height", oneUnit)
        //     .style("fill", "none")
        //     .style("stroke", "green")
        return fragment
    }
}

class VectorAnimation {
    constructor(workSpace) {
        this.workSpace = workSpace
    }

    swapElementAttr(attrName/* or attrNameList */, element1, element2) {
        function exchange(attrName) {
            // 保存 element1 的属性，将 element2 的属性赋值给 element1, 再将保存的属性赋值给 element2
            var tmp = element1.getAttribute(attrName)
            element1.setAttribute(attrName, element2.getAttribute(attrName))
            element2.setAttribute(attrName, tmp)
        }
        if (typeof attrName === "string") {
            exchange(attrName)
        } else if (typeof attrName === "object") {
            for (let i = 0; i < attrName.length; i++) {
                exchange(attrName[i])
            }
        }
    }

    swapElementInnerHTML(element1, element2) {
        var tmp = element1.innerHTML
        element1.innerHTML = element2.innerHTML
        element2.innerHTML = tmp
    }

    swapElementsAttr(elementPairList) {
        // [
        //     [attrName or attrNameList, element1, element2],
        //     [attrName or attrNameList, element1, element2],
        //     ...
        // ]
        for (let i = 0; i < elementPairList.length; i++) {
            let elementPair = elementPairList[i]
            this.swapElementAttr(elementPair[0], elementPair[1], elementPair[2])
        }
    }

    // 交换数组元素
    swapLinkedListItems(id, [fromIndex, toIndex]) {
        if (fromIndex < toIndex)
            [fromIndex, toIndex] = [toIndex, fromIndex]

        var settings = this.workSpace.settings
        let linkedList = document.getElementById(id)
        let customAttr = linkedList.customAttr
        // console.log(customAttr)

        var gList = linkedList.childNodes

        let from = gList[fromIndex]
        let to = gList[toIndex]

        var deltaX = customAttr.oneUnit * (fromIndex - toIndex);
        var deltaY = customAttr.oneUnit * 1.08

        var animateSettings = this.workSpace.settings.animation.getConf()

        // 如果是相邻的两个元素交换，或者要交换的两个元素是同一个元素
        if (Math.abs(fromIndex - toIndex) <= 1) {
            deltaY /= 2
            animateSettings.duration *= 0.6
        }

        var that = this
        let timeline = gsap.timeline({
            onStart: function () {
                displayCurrentArray(customAttr.nodes)

                consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`)
            },
            onComplete: function () {
                // 交换DOM元素中的值
                that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1])

                // 交换 node 列表中的值
                // console.log(customAttr.nodes)
                let tmp = customAttr.nodes[fromIndex]
                customAttr.nodes[fromIndex] = customAttr.nodes[toIndex]
                customAttr.nodes[toIndex] = tmp

                // console.log(customAttr.nodes)
                displayCurrentArray(customAttr.nodes)

                console.log("animation done (swap)")
            }
        }).add([
            gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
            gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
            gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }),
            gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }),
            gsap.to(from.childNodes, { ...animateSettings, y: -deltaY }),
            gsap.to(to.childNodes, { ...animateSettings, y: deltaY }),
        ]).add([
            gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }),
            gsap.to(to.childNodes, { ...animateSettings, x: deltaX }),
        ]).add([
            gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }),
            gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }),
            gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }),
            gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }),
            gsap.to(from.childNodes, { ...animateSettings, y: 0 }),
            gsap.to(to.childNodes, { ...animateSettings, y: 0 }),
        ]).add([
            // 恢复到动画之前的状态
            gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }),
            gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }),
        ])

        customAttr.gsapTimeline.add(timeline)
    }

    // 高亮数组元素
    highlightLinkedListItems(id, indexList, onStartCallback) {
        let linkedList = document.getElementById(id)
        let customAttr = linkedList.customAttr
        var gList = linkedList.childNodes

        if (typeof (indexList) === "number")
            indexList = [indexList]

        var hightlightElementList_fill = []
        var hightlightElementList_text = []
        for (let i = 0; i < indexList.length; i++) {
            const index = indexList[i];
            hightlightElementList_fill.push(gList[index].childNodes[0])
            hightlightElementList_text.push(gList[index].childNodes[1])
        }
        var animateSettings = this.workSpace.settings.animation.getConf()

        let timeline = gsap.timeline({
            onStart: function () {
                displayCurrentArray(customAttr.nodes)

                if (onStartCallback)
                    onStartCallback()
            },
            onComplete: function () {
                console.log("animation done (hightlight)")
            }
        }).add([
            gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill_focus"] }),
            gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text_focus"] }),
        ]).add([
            gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill"] }),
            gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text"] }),
        ])

        customAttr.gsapTimeline.add(timeline)
    }

    // 比较数组元素
    compareLinkedListItems(id, index1, index2) {
        this.highlightLinkedListItems(id, [index1, index2], function () {
            consoleLog(`比较索引为 ${index1} 和 ${index2} 的两个元素`)
        })
    }

    // 弹出/弹回 数组元素
    popupLinkedListItems(id, index, { popBack = false }) {
        let linkedList = document.getElementById(id)
        let customAttr = linkedList.customAttr
        var gList = linkedList.childNodes

        var deltaY = customAttr.oneUnit * 1.08
        if (popBack) deltaY = 0

        let timeline = gsap.timeline({
            onStart: function () {
                if (typeof (onStartCallback) === "function")
                    onStartCallback()
            },
            onComplete: function () {
                console.log("animation done (popup)")
            }
        }).add([
            gsap.to(gList[index], { ...this.workSpace.settings.animation.getConf(), y: deltaY }),
        ])

        customAttr.gsapTimeline.add(timeline)
    }

    // 强调/恢复 数组元素
    emphasisLinkedListItems(id, index, { popBack = false }) {
        let linkedList = document.getElementById(id)
        let customAttr = linkedList.customAttr
        var gList = linkedList.childNodes

        let eles = []
        if (typeof (index) === "object") {
            for (let i = 0; i < index.length; i++) {
                eles.push(gList[index[i]])
            }
        } else {
            eles.push(gList[index])
        }

        let timeline = gsap.timeline({
            onStart: function () {
                if (typeof (onStartCallback) === "function")
                    onStartCallback()
            },
            onComplete: function () {
                console.log("animation done (popup)")
            }
        }).add(
            popBack == false ? [
                gsap.to(eles, { ...this.workSpace.settings.animation.getConf(), y: customAttr.oneUnit * 0.4, fill: settings.colorMap["fill_focus"] }),
            ] : [
                gsap.to(eles, { ...this.workSpace.settings.animation.getConf(), y: 0, fill: settings.colorMap["fill"] }),
            ]
        )

        customAttr.gsapTimeline.add(timeline)
    }

    // 交换相邻数组元素位置（仅在水平方向呼唤，垂直方向不做调整） 【fromIndex 是y方向上突出的元素】
    exchangeLinkedListItems(id, fromIndex, toIndex) {
        if (fromIndex < toIndex)
            [fromIndex, toIndex] = [toIndex, fromIndex]

        var settings = this.workSpace.settings
        let linkedList = document.getElementById(id)
        let customAttr = linkedList.customAttr
        // console.log(customAttr)

        var gList = linkedList.childNodes

        let from = gList[fromIndex]
        let to = gList[toIndex]

        var deltaX = customAttr.oneUnit * (fromIndex - toIndex);

        var deltaY = customAttr.oneUnit * 1.08 // 要跟 popupLinkedListItems 函数中的 deltaY 完全一致
        var animateSettings = settings.animation.getConf()

        var that = this
        let timeline = gsap.timeline({
            onStart: function () {
                displayCurrentArray(customAttr.nodes)

                consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`)
            },
            onComplete: function () {
                // 交换DOM元素中的值
                that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1])

                // 交换 node 列表中的值
                // console.log(customAttr.nodes)
                let tmp = customAttr.nodes[fromIndex]
                customAttr.nodes[fromIndex] = customAttr.nodes[toIndex]
                customAttr.nodes[toIndex] = tmp

                // console.log(customAttr.nodes)
                displayCurrentArray(customAttr.nodes)

                console.log("animation done (exchange)")
            }
        }).add([
            gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }),
            gsap.to(to.childNodes, { ...animateSettings, x: deltaX }),
        ]).add([
            // 恢复到动画之前的状态
            gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0 }),
            gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0 }),
            // 由于两个元素一个突出，一个不突出，所以还要交换两个y
            gsap.to(from, { ...animateSettings, duration: 0, y: 0 }),
            gsap.to(to, { ...animateSettings, duration: 0, y: deltaY }),
        ])

        customAttr.gsapTimeline.add(timeline)
    }
}